#pragma once

#include "znx_queue.h"
#include "znx_string.h"
#include "znx_spinlock.h"
#include "znx_thread_base.h"


#define ZNX_LOG_BUF_SIZE                        (1 << 22)   // 4M
#define ZNX_LOG_MAX_PENDING_NUMS                (4)
#define ZNX_MIN_LOG_ITEM_SIZE                   (1 << 12)   // 4K
#define ZNX_LOG_FILE_PATH_MAX_LEN               2000
#define ZNX_REAL_FILE_PATH_LEN                  2112
#define ZNX_LOG_FLUSH_INTERVAL_MS               (3000)
#define ZNX_LOG_CHECK_ROLLING_INTERVAL_MS       (10000)


typedef enum {
    LOG_LEVEL_INFO      = 0,
    LOG_LEVEL_DEBUG     = 1,
    LOG_LEVEL_WARN      = 2,
    LOG_LEVEL_ERROR     = 3,
    LOG_LEVEL_FATAL     = 4
} znx_log_level_t;


typedef enum {
    LOG_ROLLING_MINUTE  = 1,
    LOG_ROLLING_HOUR    = 2,
    LOG_ROLLING_DAY     = 3
} znx_log_rolling_t;


typedef struct {
    char                    *buf;
    size_t                  used;
    znx_queue_t             queue;
    znx_spin_wait_group_t   wait_group;
} znx_log_buf_t;


static inline znx_log_buf_t *
znx_log_buf_create()
{
    znx_log_buf_t   *log_buf;
    log_buf = znx_thread_malloc(sizeof(znx_log_buf_t));
    log_buf->used = 0;
    log_buf->buf = znx_thread_malloc(ZNX_LOG_BUF_SIZE);
    znx_spin_wait_group_init(&log_buf->wait_group);
    return log_buf;
}


static inline void
znx_log_buf_free(znx_log_buf_t *log_buf)
{
    if (log_buf == NULL) {
        return;
    }

    if (log_buf->buf != NULL) {
        znx_thread_free(log_buf->buf);
    }

    znx_thread_free(log_buf);
}


typedef struct {
    size_t                      log_item_size;
    znx_log_level_t             level;
    // the log_path length must < 2000
    const char                  *log_path;
    // real_log_path length > log_path + "." + log_file_time
    char                        real_log_path[ZNX_REAL_FILE_PATH_LEN];
    int                         fd;
    znx_log_rolling_t           rolling;

    znx_spinlock_t              lock;
    znx_queue_t                 pending_bufs;
    int                         pending_nums;
    znx_log_buf_t               *active_buf;
    int64_t                     last_switch_ms;
    int64_t                     last_check_rolling_ms;

    znx_thread_t                tid;
    znx_mutex_t                 mutex;             
    znx_cond_t                  cond;

    znx_atomic_bool_t           stopped;
    znx_atomic_int64_t          busy;
    znx_bool_t                  inited;
} znx_log_t;


extern znx_log_t                def_log;


znx_bool_t znx_log_init(znx_log_t *log, size_t log_item_size,
    znx_log_level_t level, const char *log_path, znx_log_rolling_t rolling);


// for access log.
void znx_log_output(znx_log_t *log, u_char *buf, size_t size);


// for error log
void znx_log_fmt_output(znx_log_t *log, const char *level_str,
    const char *file, int line, znx_str_t *context,
    znx_bool_t abort_proc, const char *fmt, ...);


void znx_log_flush(znx_log_t *log);


void znx_log_stop(znx_log_t *log);


#define ZNX_LOG_INFO(log, context, fmt, args...)                \
    if (LOG_LEVEL_INFO >= (log)->level)                         \
        znx_log_fmt_output(log, "info", __FILE__, __LINE__, context, 0, fmt, ##args)


#define ZNX_LOG_DEBUG(log, context, fmt, args...)               \
    if (LOG_LEVEL_DEBUG >= (log)->level)                        \
        znx_log_fmt_output(log, "debug", __FILE__, __LINE__, context, 0, fmt, ##args)


#define ZNX_LOG_WARN(log, context, fmt, args...)                \
    if (LOG_LEVEL_WARN >= (log)->level)                         \
        znx_log_fmt_output(log, "warn", __FILE__, __LINE__, context, 0, fmt, ##args)


#define ZNX_LOG_ERROR(log, context, fmt, args...)               \
    if (LOG_LEVEL_ERROR >= (log)->level)                        \
        znx_log_fmt_output(log, "error", __FILE__, __LINE__, context, 0, fmt, ##args)


#define ZNX_LOG_FATAL(log, context, fmt, args...)               \
        znx_log_fmt_output(log, "fatal", __FILE__, __LINE__, context, 1, fmt, ##args)


static inline znx_bool_t
znx_def_log_init(size_t log_item_size, znx_log_level_t level,
    const char *log_path, znx_log_rolling_t rolling)
{
    return znx_log_init(&def_log, log_item_size, level, log_path, rolling);
}


static inline void
znx_def_log_flush()
{
    znx_log_flush(&def_log);
}


static inline void
znx_def_log_stop()
{
    znx_log_stop(&def_log);
}


#define ZNX_DEF_LOG_INFO(context, fmt, args...)                 \
    if (LOG_LEVEL_INFO >= def_log.level)                        \
        znx_log_fmt_output(&def_log, "info", __FILE__, __LINE__, context, 0, fmt, ##args)

#define ZNX_DEF_LOG_DEBUG(context, fmt, args...)                \
    if (LOG_LEVEL_DEBUG >= def_log.level)                       \
        znx_log_fmt_output(&def_log, "debug", __FILE__, __LINE__, context, 0, fmt, ##args)

#define ZNX_DEF_LOG_WARN(context, fmt, args...)                 \
    if (LOG_LEVEL_WARN >= def_log.level)                        \
        znx_log_fmt_output(&def_log, "warn", __FILE__, __LINE__, context, 0, fmt, ##args)

#define ZNX_DEF_LOG_ERROR(context, fmt, args...)                \
    if (LOG_LEVEL_ERROR >= def_log.level)                       \
        znx_log_fmt_output(&def_log, "error", __FILE__, __LINE__, context, 0, fmt, ##args)

#define ZNX_DEF_LOG_FATAL(context, fmt, args...)                \
        znx_log_fmt_output(&def_log, "fatal", __FILE__, __LINE__, context, 1, fmt, ##args)


